package org.paradigmshift.cumulusfracto.filesystem;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import org.alfresco.jlan.server.filesys.FileAttribute;
import org.alfresco.jlan.server.filesys.FileInfo;
import org.alfresco.jlan.server.filesys.FileOpenParams;
import org.alfresco.jlan.server.filesys.NetworkFile;
import org.alfresco.jlan.smb.SeekType;
import org.apache.log4j.Logger;
import com.google.gdata.data.DateTime;
import com.google.gdata.data.docs.DocumentListEntry;
import com.google.gdata.data.media.MediaSource;
import com.google.gdata.util.ServiceException;

public class CumulusFractoNetworkFile extends NetworkFile {
	
	private static final Logger log = Logger.getLogger( CumulusFractoNetworkFile.class );
	
	private DocumentListEntry entry;
	private BufferedInputStream inputStream;
	private BufferedOutputStream outputStream;
	
	private Long offset = 0L;
	private Long endOfFile = -1L;
	
	public static CumulusFractoNetworkFile createFile( DocumentListEntry entry, FileOpenParams params ) {
		
		CumulusFractoNetworkFile networkFile = new CumulusFractoNetworkFile( entry );
		int fileAttributes = 0;
		
		if ( entry.getCanEdit() ) {
			
			networkFile.setGrantedAccess( NetworkFile.READWRITE );
		}
		else {
			
			networkFile.setGrantedAccess( NetworkFile.READONLY );
			fileAttributes += FileAttribute.ReadOnly;
		}
		
		String resourceId = entry.getResourceId();
		String type = resourceId.substring( 0, resourceId.lastIndexOf( ":" ) );
		if ( "folder".equals( type ) ) {
			
			fileAttributes |= FileAttribute.Directory;
		}
		else {
			
			networkFile.setFileSize( entry.getQuotaBytesUsed() );
		}
		
		if ( fileAttributes == 0 ) fileAttributes = FileAttribute.NTNormal;
		networkFile.setAttributes( fileAttributes );
		
		networkFile.setCreationDate( entry.getPublished().getValue() );
		DateTime modifiedDate = entry.getEdited();
		if ( modifiedDate != null ) {
			
			networkFile.setModifyDate( modifiedDate.getValue() );
			networkFile.setAccessDate( modifiedDate.getValue() );
		}
		
		networkFile.setFullName( params.getPath() );
		
		return networkFile;
	}
	
	public CumulusFractoNetworkFile( DocumentListEntry entry ) {
		
		super( entry.getTitle().getPlainText() );
		this.entry = entry;
	}
	
	/**
	 * Close the network file.
	 */
	@Override
	public void closeFile() throws IOException {
		
		// Close the file, if used
		if ( log.isDebugEnabled() ) {
			
			log.debug( "Entered closeFile()" );
		}
		
		if ( inputStream != null ) {
			
			inputStream.close();
			inputStream = null;
		}
		
		if ( outputStream != null ) {
			
			// Close the file
			if ( getWriteCount() > 0 && entry.getCanEdit() ) {
				
				outputStream.flush();
				
				try {
					
					entry = entry.updateMedia( true );
				}
				catch ( ServiceException e ) {
					
					throw new IOException( e.getMessage() );
				}
				
				DateTime now = DateTime.now();
				entry.setUpdated( now );
				entry.setEdited( now );
			}

			outputStream.close();
			outputStream = null;
		}
		
		DateTime lastViewed = entry.getLastViewed();
		if ( lastViewed != null ) setAccessDate( lastViewed.getValue() );
		
		setClosed( true );
	}
	
	/**
	 * Flush the file.
	 * 
	 * @exception IOException
	 */
	@Override
	public void flushFile() throws IOException {

		// Flush all buffered data
		if ( log.isDebugEnabled() ) {
			
			log.debug( "Entered flushFile()" );
		}
		
		if ( outputStream != null ) {
			
			outputStream.flush();
		}
	}
	
	/**
	 * Open the file.
	 * 
	 * @param createFlag boolean
	 * @exception IOException
	 */
	@Override
	public void openFile( boolean createFlag ) throws IOException {

		if ( log.isDebugEnabled() ) {
			
			log.debug( "Entered openFile()" );
		}
		
		MediaSource mediaSource = entry.getMediaSource();
		
		if ( mediaSource != null ) {
			
			// Open the streams if they are not already
			if ( inputStream == null ) {
				
				inputStream = new BufferedInputStream( mediaSource.getInputStream() );
			}
			
			if ( outputStream == null && entry.getCanEdit() ) {
				
				outputStream = new BufferedOutputStream( mediaSource.getOutputStream() );
			}
			
			entry.setLastViewed( DateTime.now() );
			DateTime lastViewed = entry.getLastViewed();
			if ( lastViewed != null ) setAccessDate( lastViewed.getValue() );
			
			setClosed( false );
		}
		else {
			
			throw new IOException( "MediaSource was null." );
		}
	}
	
	/**
	 * Read from the file.
	 * 
	 * @param buffer byte[]
	 * @param length int
	 * @param position int
	 * @param fileOffset long
	 * @return Length of data read.
	 * @exception IOException
	 */
	@Override
	public int readFile( byte[] buffer, int length, int position, long fileOffset ) throws IOException {

		if ( log.isDebugEnabled() ) {
			
			log.debug( "Entered readFile()" );
		}
		
		// Open the file, if not already open
		if ( inputStream == null ) {
			
			openFile( false );
		}
		
		// Seek to the required file position
		if ( offset != fileOffset ) {
			
			seekFile( fileOffset, SeekType.StartOfFile );
		}
		
		// Read from the file and return length of data read
		return inputStream.read( buffer, position, length );
	}
	
	/**
	 * Seek to the specified file position.
	 * 
	 * @param pos
	 *            long
	 * @param type
	 *            int
	 * @return long
	 * @exception IOException
	 */
	@Override
	public long seekFile( long pos, int type ) throws IOException {

		if ( log.isDebugEnabled() ) {
			
			log.debug( "Entered seekFile()" );
		}
		
		// Open the file, if not already open
		if ( inputStream == null ) {
			
			openFile( false );
		}
		
		// Check if the current file position is the required file position
		switch ( type ) {
			
			case SeekType.StartOfFile:

				if ( offset != 0 ) {
					
					inputStream.reset();
				}
				offset = inputStream.skip( pos );
				break;
			
			case SeekType.CurrentPos:
				offset += inputStream.skip( pos );
				break;
			
			case SeekType.EndOfFile:

				// TODO: optimize this
				if ( pos != 0 ) {
						
					if ( endOfFile == -1 ) {
						
						// Find the end of the file
						endOfFile = 0L;
						while ( inputStream.read() != -1 ) {
							
							endOfFile++;
						}
					}
						
					pos = endOfFile - pos;
					inputStream.reset();
					offset = inputStream.skip( pos );
				}
				break;
		}
		
		// Return the new file position
		return offset;
	}
	
	/**
	 * Truncate the file
	 * 
	 * @param size
	 *            long
	 * @exception IOException
	 */
	@Override
	public void truncateFile( long size ) throws IOException {

		// Open the file, if not already open
		if ( log.isDebugEnabled() ) {
			
			log.debug( "Entered truncateFile()" );
		}
		if ( inputStream == null || outputStream == null ) {
			
			openFile( true );
		}
		else if ( this.getWriteCount() > 0 ) {
			
			flushFile();
		}
		
		Long pos = 0L;
		int c;
		while ( (c = inputStream.read()) != -1 && pos < size ) {
			
			outputStream.write( c );
			pos++;
		}
		
		while ( pos < size ) {
			
			outputStream.write( 0 );
			pos++;
		}
		
		if ( offset > size ) {
			
			offset = size;
		}
	}
	
	/**
	 * Write a block of data to the file.
	 * 
	 * @param buf
	 *            byte[]
	 * @param len
	 *            int
	 * @param pos
	 *            int
	 * @param offset
	 *            long
	 * @exception IOException
	 */
	@Override
	public void writeFile( byte[] buf, int len, int pos, long offset ) throws java.io.IOException {

		if ( log.isDebugEnabled() ) {
			
			log.debug( "Entered writeFile()" );
		}
		
		// Check for a zero length write
		if ( len > 0 ) {
			
			// Open the file, if not already open
			if ( inputStream == null || outputStream == null ) {
				
				openFile( true );
			}
			
			Long tempOffset = 0L;
			int c;
			while ( (c = inputStream.read()) != -1 ) {
				
				if ( tempOffset == offset ) {
					
					outputStream.write( buf, pos, len );
					tempOffset += len;
				}
				outputStream.write( c );
				tempOffset++;
			}
			
			// Update the write count for the file
			incrementWriteCount();
		}
	}
}
